
import 'dart:async';

import 'dart:io';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;

/// 设备内容分发服务监听端口
const _kDeviceContentDispatchPort = 10088;

/// 获取内容回调
typedef FetchContentCallback = FutureOr<Stream<List<int>>> Function(Map<String, String> query, Map<String, Object> responseHeader);

/// 设备内容分发服务
/// Author CimZzz
/// Since Elec-Communication-Protocol Alpha on 2020/7/14 上午11:16
/// 采用 Http Server 的方式下传数据
class DeviceContentDispatchService {
	/// Http 服务器
	HttpServer _server;

	/// 内容 Path 表
	Map<String, FetchContentCallback> _pathOfContents;

	/// 检查服务是否启动
	bool get _checkStarted => _server != null;

	/// Http 服务是否启动
	bool _isClosed = false;

	/// 启动设备内容分发服务
	Future<void> start() async {
		if(!_checkStarted) {
			final handler = const shelf.Pipeline()
				.addHandler(_requestMiddleware);
			_server = await io.serve(handler, '0.0.0.0', _kDeviceContentDispatchPort);
			if(_isClosed) {
				return _server.close(force: true);
			}
		}
	}

	/// 关闭内容分发服务
	void close() {
		_isClosed = true;
		_pathOfContents = null;
		_server?.close(force: true);
		_server = null;
	}

	/// 添加内容映射
	/// * path 不需要以 '/' 斜杠开头
	void addContents({String path, FetchContentCallback contentCallback}) {
		if(path != null && contentCallback != null) {
			_pathOfContents ??= {};
			_pathOfContents[path] = contentCallback;
		}
	}

	/// 清除内容映射
	void removeContents({String path}) {
		if(path != null) {
			if(_pathOfContents != null) {
				_pathOfContents.remove(path);
				if(_pathOfContents.isEmpty) {
					_pathOfContents = null;
				}
			}
		}
	}

	/// 清除全部以指定路径开头的内容映射
	void removeStartWith({String path}) {
		if(_pathOfContents != null && path != null) {
			_pathOfContents.removeWhere((path, _) => path.startsWith(path));
			if(_pathOfContents.isEmpty) {
				_pathOfContents = null;
			}
		}
	}

	/// 请求处理中间件
	FutureOr<shelf.Response> _requestMiddleware(shelf.Request request) async {
		if(request.method == 'GET') {
			final callback = _pathOfContents != null ? _pathOfContents[request.url.path] : null;
			if(callback != null) {
				final responseMap = <String, Object>{};
				final stream = await callback(request.url.queryParameters, responseMap);
				if(responseMap.isNotEmpty) {
					return shelf.Response.ok(stream, headers: responseMap);
				}
				else {
					return shelf.Response.ok(stream);
				}
			}
		}
		return shelf.Response.notFound('404 not Found');
	}

}